#import "XText.h"

/*	Some of this code is based on other emacs-like Text classes by
	Julie Zelenski, Lee Boynton, and Glen Diener.

	There's some ugly hair in here; the Text object is not very well
	designed to support this kind of stuff.  No doubt this will all be
	fixed by NextStep 9.0 ...
*/

unsigned char GetPrevious(NXStream *s)
{
     int pos, ch;
     
     pos = NXTell(s);
     if (pos <= 0) return EOF;
     NXSeek(s, --pos, NX_FROMSTART);
     ch = NXGetc(s);
     NXUngetc(s);
     return ch;
}

@implementation XText(private)
 
- scrollTo:(const NXPoint *)newOrigin
{
	// p. griffin 7/95
	// superview = ClipView
	// [superview superview] = ScrollView

	if([superview respondsTo:@selector(rawScroll:)]){
		[[superview constrainScroll:(NXPoint *)newOrigin] rawScroll:newOrigin];
		if ([[superview superview] respondsTo:@selector(reflectScroll:)]){
			[[superview superview] reflectScroll:superview];// romeo romeo
		}
	}
	return self;
}		
@end			

@implementation XText

- initFrame:(const NXRect *)frameRect text:(const char *)theText
	alignment:(int)mode
{
	// i don't understand why the compiler whines without the (char *) here
	[super initFrame:frameRect text:(const char *)theText alignment:mode];
	posHint = -1;
	xHintPos = -1;
	return self;
}

- goto:(int)pos end:(int)end mode:(int)mode
{
	int start;
	
	switch(mode) {

	case 0:		// move
		[self setSel:pos :pos];
		[self scrollSelToVisible];
		posHint = -1;
		break;

	case 1:		// delete
	case 2:		// cut
		if (pos != end) {
			start = pos;
			if (start > end)
				{ start = end; end = pos; }
			[self disableAutodisplay];
			[self setSel:start :end];
			if (mode == 1)
				[self delete:self];
			else
				[self cut:self];
		}
		posHint = -1;
		break;

	case 3:		// select
		start = pos;
		if (start > end)
			{ start = end; end = pos; }
		// The Text object can't even extend the selection without flashing,
		// unless we disable autodisplay
		if (sp0.cp != spN.cp) [self disableAutodisplay];
		[self setSel:start :end];
		posHint = pos;
		break;
	}
	xHintPos = -1;
	return self;
}

- moveChar:(int)cnt mode:(int)mode
{
	int pos, end;
	int max = [self textLength];
	
	if (sp0.cp == posHint) {
		pos = sp0.cp + cnt;
		end = spN.cp;
	} else {
		pos = spN.cp + cnt;
		end = sp0.cp;
	}
	if (pos < 0)
		pos = 0;
	else if (pos > max)
		pos = max;
	return [self goto:pos end:end mode:mode];
}

/* NXBGetc - a text stream macro to get the previous character. */

typedef struct {
    id text;
    NXTextBlock *block;
} textInfo;

static char getPrevious(NXStream *s)
{
    textInfo *info = (textInfo *) s->info;
    NXTextBlock *block = info->block->prior;

    if (!block)
		return EOF;
    s->buf_base = block->text;
    s->buf_ptr = s->buf_base + block->chars;
    s->offset -= block->chars;
    info->block = block;
    return *(--s->buf_ptr);
}

#define NXBGetc(s) \
    (((s)->buf_base == (s)->buf_ptr) ? getPrevious(s) : \
									   *(--((s)->buf_ptr)) )

- moveWord:(int)cnt mode:(int)mode
{
    NXStream *s = [self stream];
	char c;
	int i, pos, end;
	unsigned char digit_cat = charCategoryTable['0'];
	unsigned char alpha_cat = charCategoryTable['a'];
	unsigned char c_cat;
	BOOL inWord = NO;

	if (cnt == 0)
		return self;
	if (sp0.cp == posHint) {
		pos = sp0.cp;
		end = spN.cp;
	} else {
		pos = spN.cp;
		end = sp0.cp;
	}
	NXSeek(s, pos, NX_FROMSTART);
	i = (cnt<0 ? -cnt : cnt);
	while (1) {
		c = (cnt<0 ? NXBGetc(s) : NXGetc(s));
		if (c == EOF) break;
		c_cat = charCategoryTable[c];
		if (c_cat==alpha_cat || c_cat==digit_cat)
			inWord = YES;
		else if (inWord) {
			--i;
			if (i > 0)
				inWord = NO;
			else
				break;
		}
	}
	pos = NXTell(s);
	if (c != EOF)
		pos += (cnt<0 ? 1 : -1);
	return [self goto:pos end:end mode:mode];
}

/*  line is from an NXSelPt; returns the length of the line.
	too bad this requires grunging in Text's data structures */

#define LINE_LENGTH(line) \
	(self->theBreaks->breaks[(line)/sizeof(NXLineDesc)] & 0x3fff)

- moveLine:(int)cnt mode:(int)mode
{
	int pos, end, x, dir;

	if (sp0.cp == posHint) {
		pos = sp0.cp;
		end = spN.cp;
	} else {
		pos = spN.cp;
		end = sp0.cp;
	}
	if (mode != 0)
		[self disableAutodisplay];
	// collapse and normalize the selection
	[self setSel:pos :pos];
	x = (sp0.cp == xHintPos ? xHint : (sp0.cp - sp0.c1st));
	
	if (cnt < 0) {
		dir = NX_UP;
		cnt = -cnt;
	} else {
		dir = NX_DOWN;
	}
	for (; cnt > 0; --cnt)
		[self moveCaret: dir];

	pos = LINE_LENGTH(sp0.line)-1;
	if (x < pos)
		pos = x;
	pos += sp0.c1st;
	[self goto:pos end:end mode:mode];
	xHintPos = pos;
	xHint = x;
	return self;
}

- lineBegin:(int)mode
{
	int pos, end;

	if (sp0.cp == posHint) {
		pos = sp0.c1st;
		end = spN.cp;
	} else {
		pos = spN.c1st;
		// Text is inconsistent about what line it thinks we're on
		if (spN.cp == (spN.c1st + LINE_LENGTH(spN.line))){
			pos = spN.cp;
		}
		end = sp0.cp;
	}
	return [self goto:pos end:end mode:mode];
}

- lineEnd:(int)mode
{
	NXSelPt *sp;
	int pos, end;

	if (sp0.cp == posHint) {
		sp = &sp0;
		end = spN.cp;
	} else {
		// need to correct for TBD
		sp = &spN;
		end = sp0.cp;
	}
	pos = sp->c1st + LINE_LENGTH(sp->line) - 1;
	if (pos < sp->cp) {
		// Text is being flakey again; we really want to be on the next line
		// this is pretty gross
		pos = sp->line;
		if (theBreaks->breaks[pos/sizeof(NXLineDesc)] < 0)
			pos += sizeof(NXHeightChange);
		else
			pos += sizeof(NXLineDesc);
		pos = sp->cp + LINE_LENGTH(pos) - 1;
	}
	if ((pos == sp->cp) && (mode != 0))
		++pos;
	return [self goto:pos end:end mode:mode];
}

- docBegin:(int)mode
{
	return [self goto:0
				 end:(sp0.cp == posHint ? spN.cp : sp0.cp)
				 mode:mode];
}

- docEnd:(int)mode
{
	return [self goto:[self textLength]
				 end:(sp0.cp == posHint ? spN.cp : sp0.cp)
				 mode:mode];
}

- collapseSel:(int)dir
{
	int pos;

	if ((dir < 0) || ((dir == 0) && (sp0.cp == posHint)))
		pos = sp0.cp;
	else
		pos = spN.cp;
	return [self goto:pos end:pos mode:0];
}

- transChars
{
	int pos = sp0.cp;
	char buf[2], temp;

	if (pos == spN.cp) {
		if (pos == (sp0.c1st + LINE_LENGTH(sp0.line) - 1))
			--pos;
		if (pos > 0)
			if ([self getSubstring:buf start:pos-1 length:2] == 2) {
				temp = buf[1]; buf[1] = buf[0]; buf[0] = temp;
				[self disableAutodisplay];
				[self setSel:pos-1 :pos+1];
				[self replaceSel:buf length:2];
				return self;
			}
	}
	NXBeep();
	return self;
}

- openLine
{
	int pos = sp0.cp;

	// don't do anything if there's a non-empty selection
	if (pos == spN.cp) {
		[self replaceSel:"\n"];
		[self setSel:pos :pos];
	} else
		NXBeep();
	return self;
}

			
- scroll:(int)pages :(int)lines
{
	NXRect r;

	// if our superview isn't a ClipView, we can't scroll
	if ([superview respondsTo:@selector(rawScroll:)]) {
		[superview getBounds:&r];
		r.origin.y += pages*r.size.height + lines*[self lineHeight];
		[self scrollTo:&r.origin];
	} else
		NXBeep();
	return self;
}

- scrollIfRO:(int)pages :(int)lines
{
	if (![self isEditable])
		return [self scroll:pages :lines];
	else
		return nil;
}

- insertChar:(NXEvent *)event
{
	char c;

	c = event->data.key.charCode;
	[self replaceSel:&c length:1];
	return self;
}

- insertNextChar
{
	static id action = nil;

	if (!action)
		action = [[XTEventMsgAction allocFromZone:[NXApp zone]]
							initSel:@selector(insertChar:)];
	nextAction = action;
	return self;
}

- autoIndent
{
	int pos, end;
	unsigned char buf[2];

	// don't do anything if there's a non-empty selection
	if (sp0.cp != spN.cp) {
		NXBeep();
		return self;
	}
	
	if (sp0.cp == posHint) {
		pos = sp0.c1st;
		end = spN.cp;
	} else {
		pos = spN.c1st;
		// Text is inconsistent about what line it thinks we're on
		if (spN.cp == (spN.c1st + LINE_LENGTH(spN.line))){
			pos = spN.cp;
		}
		end = sp0.cp;
	}
	
	[[self hideCaret] disableAutodisplay];  // no need to display yet
	[self replaceSel:"\n" length:1];

	while ([self getSubstring:buf start:pos++ length:1] != -1){
		if(buf[0] == ' ' || buf[0] == '\t') [self replaceSel:buf length:1];
		else if(pos == end) break;
		else break;
	}
	
	[[self setAutodisplay:YES] displayIfNeeded];

	/* scroll down to the correct line */
	if ([superview respondsTo:@selector(rawScroll:)]) {
		[self scroll:0 :1];
		[self calcLine];
	}
	 
	return self;
}

- match:(unsigned char *)LR
{
    NXRect oldRect, newRect;
	unsigned char buf[2];
	int count, left_pos, right_pos, utime;		

	right_pos = sp0.cp; 
	left_pos = right_pos-1;
	count = 1;
	utime = 300000;

	/*	don't do anything if there's a non-empty selection
	 * 	or not two character */
	
	if (sp0.cp != spN.cp || strlen(LR) != 2) return self;
	
	/* at the beginning of file ? */
	if (left_pos < 0){
		[self replaceSel:&LR[1] length:1];
		return self;		
	}
	   
	/* search for the left character */ 
	while([self getSubstring:buf start:left_pos length:1] != -1){
		if(buf[0] == LR[0]) count--;
		else if (buf[0] == LR[1])  count++;
		if(count == 0) break;
		if(left_pos-- == 0) break;
	}
	
	if(count != 0)  {
		[self replaceSel:&LR[1] length:1];
		return self;
	}


	[self goto:left_pos end:left_pos+1 mode:3];

	/* if our superview isn't a ClipView, no scrolling */
	if ([superview respondsTo:@selector(rawScroll:)]) {
		[superview getBounds:&oldRect];

		/* scroll to selection */
		[self scrollSelToVisible];
		[superview getBounds:&newRect];
		
		/* add some time for viewing if the text is scrolled */
		if(newRect.origin.y != oldRect.origin.y) utime +=300000;		
	}

	[[self window] display];
	
	usleep(utime);
	
	[self goto:right_pos end:right_pos mode:0];	
		
	/* scrollBack */
	if ([superview respondsTo:@selector(rawScroll:)]) {
		[self scrollTo:&oldRect.origin];
	}
		
	[self replaceSel:&LR[1] length:1];
	
	return self;
}

- insertKeyCombination:(NXEvent *)event
{
	char code[9];
	int code_len = 0;
	int cc = event->data.key.charCode;
	int f_digit = event->data.key.charCode >> 4;
	int s_digit = event->data.key.charCode & 0xf;

	if ((event->flags & NX_ALPHASHIFTMASK) &&
	    !(event->flags & NX_SHIFTMASK))     code[code_len++] = 'l';
	if (event->flags & NX_SHIFTMASK)     	code[code_len++] = 's';
	if (event->flags & NX_CONTROLMASK)   	code[code_len++] = 'c';
	if (event->flags & NX_ALTERNATEMASK) 	code[code_len++] = 'a';
	if (event->flags & NX_COMMANDMASK)   	code[code_len++] = 'm';
	if (event->flags & NX_NUMERICPADMASK)	code[code_len++] = 'n';
	if (event->flags & NX_HELPMASK)			code[code_len++] = 'h';

	if(NXIsPrint(cc) && !NXIsSpace(cc) && !NXIsCntrl(cc)){
		/* should be able to print this character */
		code[code_len++] = 0x27; // '
		code[code_len++] = cc;
	}
	else if(NXIsCntrl(cc) && (event->flags & NX_CONTROLMASK) 
			&& !NXIsSpace(cc) && (cc <= 0x1F)){
		/* ordinary control character */
		code[code_len++] = 0x27; // '
		code[code_len++] = (event->flags & NX_SHIFTMASK) ? cc + 0x40: cc+ 0x60;
	}
	else{
	    /* cannot print, replace with hex code */
		code[code_len++] = (f_digit < 10) ? '0' + f_digit: 'A'+ f_digit-10;
		code[code_len++] = (s_digit < 10) ? '0'+ s_digit : 'A'+ s_digit-10;
    }
	
	[self replaceSel:code length:code_len];
	return self;
}
 
- insertKeyCombOfNextKey
{
	static id action = nil;

	if (!action)
		action = [[XTEventMsgAction alloc]
						initSel:@selector(insertKeyCombination:)];
	nextAction = action;
	return self;
}

- selectText:sender
{
	[self setSel:0:0];
	
	return self;
}

@end
